// 阻塞队列，当消费者去读取数据时，如果队列为空，消费者就阻塞者；生产者同理，当队列满了，生产者也阻塞住，不生产任务
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
#include <mutex>
#include "lockGuard.hpp"

const int gDefaultCap = 5;

template <class T>
class BlockQueue // 我们可以往阻塞队列里放数据让消费者去拿
{
public:
    BlockQueue(int capacity = gDefaultCap)
        : _capacity(gDefaultCap)
    {
        // 构造时初始化锁的条件变量
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_Empty, nullptr);
        pthread_cond_init(&_Full, nullptr);
        // low_water = _capacity / 3;
        // high_water = (_capacity * 2) / 3;
    }

    void push(const T &in)
    {
        LockGuard lockguard(&_mtx); // 自动调用构造，自动加锁，然后函数结束后自动析构
        // 1，先检测当前临界资源是否满足访问条件
        while (_bq.size() == _capacity) // 如果队列满了，就阻塞住，Full条件变量由pop控制，当消费者消费数据也就是pop时，生产者才开始生产
            pthread_cond_wait(&_Full, &_mtx);
        // 检测临界资源其实也是在访问临界资源，所以需要在临界区中，但是这时候我是持有锁的，这时候我等待了，锁没有释放，这时候消费者就无法拿到锁进行消费
        // pthread_cond_wait接口的第二个参数是一个锁，表示成功调用wait并阻塞之后，传入的锁会自动释放，当我被唤醒时，从被阻塞挂起的位置唤醒
        // 当我们被唤醒的时候，pthread_cond_wait会自动给线程获取锁，且这个过程是原子的
        // 但是wait也是一个函数，只要是函数调用，就有可能“失败”，失败后就没有被阻塞了，所以可能存在伪唤醒情况，就是唤醒条件没满足，线程就被唤醒了，所以我们不能用if判断，应该用while判断
        // 2，访问临界资源，100%确定资源是就绪的
        _bq.push(in); // 条件彻底满足时，再让生产者往队列放数据
        // if (_bq.size() < low_water) // 如果任务数小于指定值，通知生产者赶紧来生产
        pthread_cond_signal(&_Empty);
    }
    void pop(T *out)
    {
        LockGuard lockguard(&_mtx);
        while (_bq.size() == 0) // 如果队列为空，消费者就阻塞住，Empty条件变量由生产者控制，当生产者生产数据也就是push时，通知消费者开始消费数据
            pthread_cond_wait(&_Empty, &_mtx);
        *out = _bq.front();
        _bq.pop();
        // if (_bq.size() > high_water) // 如果队列里任务数大于指定值了，通知消费者赶紧来消费
        pthread_cond_signal(&_Full);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_Empty);
        pthread_cond_destroy(&_Full);
    }

private:
    std::queue<T> _bq;     // 阻塞队列，里面放模板，是生产者和消费者的 “共享资源”
    int _capacity;         // 表示阻塞队列的容量上限，表示队列当中的极值，与queue.size()无关
    pthread_mutex_t _mtx;  // 通过互斥锁保证队列安全，因为STL容器本来不是线程安全的，需要我们自己保护
    pthread_cond_t _Empty; // 条件变量，表示阻塞队列是否为空
    pthread_cond_t _Full;  // 条件变量，表示阻塞队列是否为满

    // int low_water = 0;
    // int high_water = 0;
};
